<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */
declare (strict_types=1);

namespace lflow\model;

use lflow\Action;
use lflow\helper\StringHelper;
use lflow\Logger;
use lflow\workflow\Execution;
use lflow\WorkFlowException;
use ReflectionClass;

/**
 * 节点元素（存在输入输出的变迁）
 *
 * @author Mr.April
 * @since  1.0
 */
abstract class NodeModel extends BaseModel implements Action
{

    /**
     * 输入变迁集合
     */
    public array $inputs = [];

    /**
     * 输出变迁集合
     */

    public array $outputs = [];

    /**
     * 局部前置拦截器
     */
    private string $preInterceptors;

    /**
     * 局部后置拦截器
     */
    private string $postInterceptors;

    /**
     * 前置局部拦截器实例集合
     */
    private array $preInterceptorList = [];

    /**
     * 后置局部拦截器实例集合
     */
    private array $postInterceptorList = [];

    /**
     * 具体节点模型需要完成的执行逻辑
     *
     * @param \lflow\workflow\Execution $execution
     *
     * @return mixed
     */
    abstract public function exec(Execution $execution): void;

    /**
     * 对执行逻辑增加前置、后置拦截处理
     *
     * @param \lflow\workflow\Execution $execution
     *
     * @return void
     * @throws \lflow\WorkFlowException
     */
    public function execute(Execution $execution): void
    {
        $this->intercept($this->preInterceptorList, $execution);
        $this->exec($execution);
        $this->intercept($this->postInterceptorList, $execution);
    }

    /**
     * 运行变迁继续执行
     *
     * @param \lflow\workflow\Execution $execution 执行对象
     */
    public function runOutTransition(Execution $execution): void
    {
        foreach ($this->getOutputs() as $Transition) {
            $Transition->setEnabled(true);
            $Transition->execute($execution);
        }
    }

    /**
     * 拦截方法
     *
     * @param array                     $interceptorList 拦截器列表
     * @param \lflow\workflow\Execution $execution       执行对象
     *
     * @return array|void
     * @throws \lflow\WorkFlowException
     */
    public function intercept(array $interceptorList, Execution $execution): void
    {
        try {
            foreach ($interceptorList as $interceptor) {
                $interceptor->intercept($interceptor);
            }
        } catch (WorkFlowException $e) {
            Logger:: error("拦截器执行失败=" . json_encode($e->getMessage()));
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * * 根据父节点模型、当前节点模型判断是否可退回。可退回条件：
     * 1、满足中间无fork、join、subprocess模型
     * 2、满足父节点模型如果为任务模型时，参与类型为any
     *
     * @param \lflow\model\NodeModel $current
     * @param \lflow\model\NodeModel $parent
     *
     * @return bool
     */
    public function canRejected(NodeModel $current, NodeModel $parent): bool
    {
        if ($parent instanceof TaskModel && !$parent->isPerformAny()) {
            return false;
        }
        $result = false;
        foreach ($current->getInputs() as $tm) {
            $source = $tm->getSource();
            if ($source == $parent) {
                return true;
            }

            if ($source instanceof ForkModel || $source instanceof JoinModel || $source instanceof SubProcessModel || $source instanceof StartModel) {
                continue;
            }
            $result = $result || $this->canRejected($source, $parent);
        }
        return $result;
    }

    public function getNextModels(object $clazz): array
    {
        $temp   = [];
        $models = [];
        foreach ($this->getOutputs() as $tm) {
            $this->addNextModels($models, $tm, $clazz, $temp);
        }
        return $models;
    }

    protected function addNextModels(&$models, $tm, $clazz, $temp): void
    {
        if (count($temp) != 0 && in_array($tm->getTo(), array_keys($temp))) {
            return;
        }
        $obj = new ReflectionClass($clazz);
        if ($obj->isInstance($tm->getTarget())) {
            $models[] = $tm->getTarget();
        } else {
            foreach ($tm->getTarget()->getOutputs() as $tm2) {
                $temp[$tm->getTo()] = $tm->getTarget();
                $this->addNextModels($models, $tm2, $clazz, $temp);
            }
        }
    }

    public function setInputs($inputs): void
    {
        $this->inputs = $inputs;
    }

    public function getInputs(): array
    {
        return $this->inputs;
    }

    public function setOutputs(array $outputs): void
    {
        $this->outputs = $outputs;
    }

    public function getOutputs(): array
    {
        return $this->outputs ?? [];
    }

    public function getPreInterceptors(): string
    {
        return $this->preInterceptors;
    }

    public function setPreInterceptors(string $preInterceptors): void
    {
        $this->preInterceptors = $preInterceptors;
        if (StringHelper::isNotEmpty($preInterceptors)) {
            foreach (explode(',', $preInterceptors) as $interceptor) {
                if ($interceptor != null) {
                    $ans                   = $this->getPreInterceptors();
                    $this->preInterceptors = $ans . ',' . $interceptor;
                }
            }
        }
    }

    public function getPostInterceptors(): string
    {
        return $this->postInterceptors;
    }

    public function setPostInterceptors(string $postInterceptors): void
    {
        $this->postInterceptors = $postInterceptors;
        if (StringHelper::isNotEmpty($postInterceptors)) {
            foreach (explode(',', $postInterceptors) as $interceptor) {
                if ($interceptor != null) {
                    $ans                   = $this->getPreInterceptors();
                    $this->preInterceptors = $ans . ',' . $interceptor;
                }
            }
        }
    }
}
